#include <stdio.h>
#include "uapi_usb.h"
#include "common.h"

libusb_context *g_usb_ctx;

int32_t uapi_usb_init(void)
{
    int32_t ret;
    ret = libusb_init(&g_usb_ctx);
    if (ret < 0) {
        OFP_LOGE("libusb_init error \n");
        ret = UAPI_ERROR_ACCESS;
    } else {
        ret =UAPI_SUCCESS;
    }
    return ret;
}

void uapi_usb_exit(void)
{
    libusb_exit(g_usb_ctx);
}

static void uapi_get_usb_endpoint_number(struct libusb_device_descriptor desc, libusb_device *dev,
    uint8_t *interface_number, uint8_t *endpoint_in, uint8_t *endpoint_out)
{
    uint8_t j, l, n, m;
    struct libusb_config_descriptor *usb_cfg_desc;
    uint8_t endpointAddress;
    int r = 0;

    for(j = 0; j < desc.bNumConfigurations; j++)
    {
        r = libusb_get_config_descriptor(dev, j, &usb_cfg_desc);

        if(r < 0)
        {
            continue;
        }

        for(l = 0; l < usb_cfg_desc->bNumInterfaces; l++)
        {
            for(n = 0; n < usb_cfg_desc->interface[l].num_altsetting; n++)
            {
                *interface_number = usb_cfg_desc->interface[l].altsetting[n].bInterfaceNumber;
                OFP_LOGI("interface_number: 0x%02x \n", *interface_number);
                for(m = 0; m < usb_cfg_desc->interface[l].altsetting[n].bNumEndpoints; m++)
                {

                    endpointAddress = usb_cfg_desc->interface[l].altsetting[n].endpoint[m].bEndpointAddress;
                    if((0 < endpointAddress) && (LIBUSB_ENDPOINT_IN & endpointAddress))
                    {
                        *endpoint_in = endpointAddress;
                        OFP_LOGI("endpoint_in: 0x%02x \n", *endpoint_in);
                    }
                    else if((0 < endpointAddress) && !(LIBUSB_ENDPOINT_IN & endpointAddress))
                    {
                        *endpoint_out = endpointAddress;
                        OFP_LOGI("endpoint_out: 0x%02x \n", *endpoint_out);
                    }
                }
            }
        }
        libusb_free_config_descriptor(usb_cfg_desc);
    }
}

int32_t uapi_open_usb_handle_by_vid(uint16_t vendor_id, uapi_usb_handle *handle)
{
    struct libusb_device **devs;
    struct libusb_device *found = NULL;
    struct libusb_device *dev;
    struct libusb_device_handle *dev_handle = NULL;
    struct libusb_device_descriptor desc;
    size_t i = 0;
    int r = UAPI_SUCCESS;;
    OFP_LOGD("uapi_open_usb_handle_by_vid ================> enter\n");

    if (libusb_get_device_list(g_usb_ctx, &devs) < 0) {
        OFP_LOGE("uapi_open_usb_handle_by_vid--->libusb_get_device_list error\n");
        return -1;
    }

    while ((dev = devs[i++]) != NULL) {
        r = libusb_get_device_descriptor(dev, &desc);
        if (r < 0) {
            OFP_LOGE("uapi_open_usb_handle_by_vid--->libusb_get_device_descriptor error\n");
            goto OUT;
        }

        if (desc.idVendor == vendor_id) {
            found = dev;
            uapi_get_usb_endpoint_number(desc, dev, &(handle->interface_number), &(handle->endpoint_in), &(handle->endpoint_out));
            break;
        }
    }

    if (found) {
        r = libusb_open(found, &dev_handle);
        if (r < 0) {
            dev_handle = NULL;
            OFP_LOGE("uapi_open_usb_handle_by_vid--->libusb_open error\n");
            goto OUT;
        }
    } else {
        OFP_LOGE("uapi_open_usb_handle_by_vid  no descriptor error\n");
        r = -1;
        goto OUT;
    }

    r = libusb_claim_interface(dev_handle, handle->interface_number);
    if(r < 0) {
        OFP_LOGE("uapi_open_usb_handle_by_vid--->libusb_claim_interface error\n");
        r = libusb_detach_kernel_driver(dev_handle, handle->interface_number);
        if(r < 0) {
            OFP_LOGE("uapi_open_usb_handle_by_vid--->libusb_detach_kernel_driver error\n");
            goto OUT;
        }
        r = libusb_claim_interface(dev_handle, handle->interface_number);
        if(r < 0) {
            OFP_LOGE("uapi_open_usb_handle_by_vid--->second call libusb_claim_interface error\n");
            goto OUT;
        }
    }

OUT:
    libusb_free_device_list(devs, 1);
    handle->handle = dev_handle;
    OFP_LOGD("uapi_open_usb_handle_by_vid ret = %d \n", r);
    OFP_LOGD("uapi_open_usb_handle_by_vid ================> exit\n");
    return r;
}

void uapi_close_usb_handle(uapi_usb_handle *handle)
{

    if ((handle == NULL) || (handle->handle == NULL)) {
        OFP_LOGE("uapi_close_usb_handle handle null, error param \n");
        return;
    }
    if (handle->handle != NULL) {
        libusb_close(handle->handle);
        handle->handle = NULL;
    }
}


int32_t uapi_usb_bulk_transfer(uapi_usb_handle *handle, uapi_usb_cmd *cmd)
{
    uint8_t endpoint_out = 0;
    uint8_t endpoint_in = 0;

    if((cmd == NULL) || (handle == NULL) ||(handle->handle == NULL)) {
        OFP_LOGE("uapi_usb_bulk_transfer the param is error\n");
        return UAPI_ERROR_INVALID_PARAM;
    }


    endpoint_out = handle->endpoint_out;
    endpoint_in = handle->endpoint_in;

    if((cmd->need_write_len > 0) && (endpoint_out != 0) && (endpoint_out <= UAPI_ENDPOINT_NUM_MAX)) {
        if(0 != libusb_bulk_transfer(handle->handle, endpoint_out, cmd->buf, cmd->need_write_len, &(cmd->actual_write_len), cmd->timeout)) {
            OFP_LOGE("libusb_bulk_transfer write failed!!!\n");
            cmd->need_write_len = 0;
            return UAPI_ERROR_TRANSFER;
        }

        if (cmd->need_write_len != cmd->actual_write_len) {
            OFP_LOGE("libusb_bulk_transfer write len failed!!!, need_write_len:%u, actual_write_len:%u \n",  \
                cmd->need_write_len, cmd->actual_write_len);
            return UAPI_ERROR_TRANSFER_LEN;
        }

    }

    if((cmd->need_read_len > 0) && ((UAPI_ENDPOINT_DIR_MASK & endpoint_in) == UAPI_ENDPOINT_DIR_MASK) \
        && ((endpoint_in & (~UAPI_ENDPOINT_DIR_MASK)) != 0) &&
        ((endpoint_in & (~UAPI_ENDPOINT_DIR_MASK)) <= UAPI_ENDPOINT_NUM_MAX)) {
        if(0 != libusb_bulk_transfer(handle->handle, endpoint_in, cmd->buf, cmd->need_read_len, &(cmd->actual_read_len), cmd->timeout)) {
            OFP_LOGE("libusb_bulk_transfer read failed!!!\n");
            cmd->need_read_len = 0;
            return UAPI_ERROR_TRANSFER;
        }

        if (cmd->need_read_len != cmd->actual_read_len) {
            OFP_LOGE("libusb_bulk_transfer read len failed!!!, need_read_len:%u actual_read_len:%u \n",  \
                cmd->need_read_len, cmd->actual_read_len);

            return UAPI_ERROR_TRANSFER_LEN;
        }
    }

    return UAPI_SUCCESS;
}

static int32_t uapi_usb_prepare_transfer(uapi_usb_handle *handle, uint8_t bulk_dir, uint32_t len)
{
    uint8_t buf[8] = {0};
    uapi_usb_cmd cmd = {0};
    int32_t ret = UAPI_ERROR_OTHER;

    buf[0] = 'U';
    buf[1] = 'S';
    buf[2] = 'B';
    buf[3] = 'C';
    buf[4] = bulk_dir;
    buf[5] = len & 0xff;
    buf[6] = (len >> 8) & 0xff;
    buf[7] = (len >> 16) & 0xff;
    cmd.buf = buf;
    cmd.need_write_len = 8 ;

    cmd.need_read_len = 0;
    cmd.timeout = UAPI_BULK_TRANSFER_TIMEOUT;

    ret = uapi_usb_bulk_transfer(handle, &cmd);
    if (ret < 0) {
       OFP_LOGE("uapi_usb_prepare_transfer failed!!!,  error code: %d \n", ret);
    }

    return ret;

}


static int32_t uapi_usb_bulk_transfer_msg(uapi_usb_handle *handle, uint8_t bulk_dir, uint8_t *buf, uint32_t len)
{
    uapi_usb_cmd cmd = {0};
    int32_t ret = UAPI_ERROR_OTHER;

    cmd.buf = buf;
    cmd.need_write_len = (bulk_dir == UAPI_BULK_IO_PHASE_OUT) ? len : 0;
    cmd.need_read_len = (bulk_dir == UAPI_BULK_IO_PHASE_IN) ? len : 0;;
    cmd.timeout = UAPI_BULK_TRANSFER_TIMEOUT;

    ret = uapi_usb_bulk_transfer(handle, &cmd);
    if (ret < 0) {
       OFP_LOGE("uapi_usb_bulk_transfer_msg failed!!! error code: %d \n", ret);
    }

    return ret;
}

static int32_t uapi_usb_bulk_reply_ack(uapi_usb_handle *handle, uint8_t bulk_dir,  uint32_t len)
{
    uapi_usb_cmd cmd = {0};
    int32_t ret = UAPI_ERROR_OTHER;

    uint8_t buf[8] = {0};

    cmd.buf = buf;
    cmd.need_write_len = (bulk_dir == UAPI_BULK_IO_PHASE_OUT) ? len : 0;
    cmd.need_read_len = (bulk_dir == UAPI_BULK_IO_PHASE_IN) ? len : 0;;
    cmd.timeout = UAPI_BULK_TRANSFER_TIMEOUT;

    ret = uapi_usb_bulk_transfer(handle, &cmd);
    if (ret < 0) {
       OFP_LOGE("uapi_usb_bulk_reply_ack failed !!! error code: %d \n", ret);
       return ret;
    }

    if ((cmd.buf[0] != 'U') || (cmd.buf[1] != 'S') || (cmd.buf[2] != 'B') ||(cmd.buf[3] != 'S')) {
        OFP_LOGE("ofp_usb_bulk_transfer ack error !!!\r\n");
        return UAPI_ERROR_ACK;
    }

    return UAPI_SUCCESS;
}

int32_t uapi_usb_send_msg(uapi_usb_handle *handle, uint8_t *buf, uint32_t len)
{
    uapi_usb_cmd cmd = {0};
    cmd.buf = buf;
    int32_t ret = UAPI_ERROR_OTHER;

    OFP_LOGD("uapi_usb_send_msg ================> enter\n");

    ret = uapi_usb_prepare_transfer(handle, UAPI_BULK_IO_PHASE_OUT, len);
    if (ret < 0) {
        OFP_LOGE("uapi_usb_send_msg---> uapi_usb_prepare_transfer failed\n");
        goto OUT;
    }

    ret = uapi_usb_bulk_transfer_msg(handle, UAPI_BULK_IO_PHASE_OUT, buf, len);
    if (ret < 0) {

        OFP_LOGE("uapi_usb_send_msg---> uapi_usb_bulk_transfer_msg failed\n");
        goto OUT;
    }

    ret = uapi_usb_bulk_reply_ack(handle, UAPI_BULK_IO_PHASE_IN, UAPI_BULK_ACK_LEN);
    if (ret < 0) {
        OFP_LOGE("uapi_usb_send_msg---> uapi_usb_bulk_reply_ack failed\n");
        goto OUT;
    }

OUT:
    OFP_LOGD("uapi_usb_send_msg ret = %d \n", ret);

    OFP_LOGD("uapi_usb_send_msg <================ exit\n");
    return ret;
}


int32_t uapi_usb_recv_msg(uapi_usb_handle *handle, uint8_t *buf, uint32_t len)
{
    int32_t ret = UAPI_ERROR_OTHER;
    int32_t i = 0;

    OFP_LOGD("uapi_usb_recv_msg ================> enter\n");

    ret = uapi_usb_prepare_transfer(handle, UAPI_BULK_IO_PHASE_IN, len);
    if (ret < 0) {
        goto OUT;
    }

    ret = uapi_usb_bulk_transfer_msg(handle, UAPI_BULK_IO_PHASE_IN, buf, len);
    if (ret < 0) {
        goto OUT;
    }

    OFP_LOGD("uapi_usb_recv_msg  data start len:%d : \n", len);
    for (i = 0; i < len; i++) {
        printf("0x%02x ", buf[i]);
    }
    printf("\n");
    OFP_LOGD("uapi_usb_recv_msg  data end: \n");

    ret = uapi_usb_bulk_reply_ack(handle, UAPI_BULK_IO_PHASE_IN, UAPI_BULK_ACK_LEN);
    if (ret < 0) {
        goto OUT;
    }

OUT:
    OFP_LOGD("uapi_usb_recv_msg ret = %d \n", ret);

    OFP_LOGD("uapi_usb_recv_msg <================ exit\n");
    return ret;
}




